//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/io/ctextreader.h>
#include <buola/io/cfile.h>
#include <buola/functors/predicates/char.h>
#include <buola/io.h>

namespace buola { namespace io {

CTextReader::CTextReader(const CURI &pURI,EMode pMode)
    :   mStream(io::CFile(pURI).GetBufferStream(pMode))
{
}

namespace {
    
    struct AIntExtractor
    {
        AIntExtractor()
            :   mAcceptSign(true)
            ,   mValid(false)
            ,   mSign(1)
            ,   mValue(0)
        {}
        
        bool operator()(char pChar)
        {
            switch(pChar)
            {
                case '-':
                    mSign=-1;
                case '+':
                    if(!mAcceptSign||mValid) return true;
                    mAcceptSign=false;
                    return false;
                case '0'...'9':
                    mValue*=10;
                    mValue+=pChar-'0';
                    mValid=true;
                    return false;
                default:
                    return true;
            }
        }
        
        bool Valid() const      {   return mValid;  }
        int64_t Value() const   {   return mValue*mSign;  }
        
    private:
        bool mAcceptSign;
        bool mValid;
        int mSign;
        int64_t mValue;
    };
    
    struct AUIntExtractor
    {
        AUIntExtractor()
            :   mAcceptSign(true)
            ,   mValid(false)
            ,   mValue(0)
        {}
        
        bool operator()(char pChar)
        {
            switch(pChar)
            {
                case '+':
                    if(!mAcceptSign||mValid) return true;
                    mAcceptSign=false;
                    return false;
                case '0'...'9':
                    mValue*=10;
                    mValue+=pChar-'0';
                    mValid=true;
                    return false;
                default:
                    return true;
            }
        }
        
        bool Valid() const      {   return mValid;  }
        uint64_t Value() const   {   return mValue;  }
        
    private:
        bool mAcceptSign;
        bool mValid;
        uint64_t mValue;
    };
    
    struct ARealExtractor
    {
    private:
        enum EState
        {
            BEGIN,
            AFTERSIGN,
            AFTERDOT,
            AFTERE,
            AFTERESIGN,
        };
    public:
        ARealExtractor()
            :   mState(BEGIN)
            ,   mValid(false)
            ,   mNum(0)
            ,   mDen(1)
            ,   mExp(0)
            ,   mExpSign(1)
        {}
        
        bool operator()(char pChar)
        {
            switch(pChar)
            {
                case '-':
                    if(mState==BEGIN)
                    {
                        mDen=-1;
                        mState=AFTERSIGN;
                        return false;
                    }
                    else if(mState==AFTERE)
                    {
                        mExpSign=-1;
                        mState=AFTERESIGN;
                        return false;
                    }
                    return true;
                case '+':
                    if(mState==BEGIN)
                    {
                        mState=AFTERSIGN;
                        return false;
                    }
                    else if(mState==AFTERE)
                    {
                        mState=AFTERESIGN;
                        return false;
                    }
                    return true;
                case '.':
                    if(mState<AFTERDOT)
                    {
                        mState=AFTERDOT;
                        return false;
                    }
                    return true;
                case '0'...'9':
                    if(mState<AFTERE)
                    {
                        mValid=true;
                        mNum*=10;
                        mNum+=pChar-'0';
                        if(mState==AFTERDOT)
                            mDen*=10;
                        return false;
                    }
                    else
                    {
                        mValid=true;
                        mExp*=10;
                        mExp+=pChar-'0';
                        return false;
                    }
                case 'e':
                case 'E':
                    if(mValid&&mState<AFTERE)
                    {
                        mState=AFTERE;
                        mValid=false;
                        return false;
                    }
                    return true;
                default:
                    return true;
            }
        }
        
        bool Valid() const      {   return mValid;  }
        int64_t Value() const   
        {   
            if(mDen==1)
            {
                if(mExp)
                    return mNum*pow10(mExpSign*mExp);
                else
                    return mNum;
            }
            else
            {
                if(mExp)
                    return mNum/mDen*pow10(mExpSign*mExp);  
                else
                    return mNum/mDen;
            }
        }
        
    private:
        EState mState;
        bool mValid;
        double mNum;
        double mDen;
        int mExp;
        int mExpSign;
    };
    
    template<typename tExtractor,typename tNumber>
    CTextReader &extract_number(CTextReader &pR,tNumber &pNumber)
    {
        pR.Stream()->IgnoreUntil(!fn::is_ascii_space(),false);
        tExtractor lExtractor;
        pR.Stream()->IgnoreUntil(std::ref(lExtractor),false);
        if(!lExtractor.Valid())
            throw XData("can't extract number from CTextReader");
        pNumber=lExtractor.Value();
        return pR;
    }

/*namespace*/ }

CTextReader &CTextReader::operator>>(std::string &pString)
{
    mStream->IgnoreUntil(!fn::is_ascii_space(),false);
    CResult lResult=mStream->ReadUntil(pString,fn::is_ascii_space(),EReadFlags::NONE);
    if(!lResult.Count())
        throw XData("can't extract string from CTextReader");
    return *this;
}

CTextReader &CTextReader::operator>>(std::u16string &pString)
{
    std::string lString;
    operator>>(lString);
    pString=utf16(lString);
    return *this;
}

CTextReader &CTextReader::operator>>(std::wstring &pString)
{
    std::string lString;
    operator>>(lString);
    pString=utf32(lString);
    return *this;
}

CTextReader &CTextReader::operator>>(int8_t &pInt)
{
    return extract_number<AIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(uint8_t &pInt)
{
    return extract_number<AUIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(int16_t &pInt)
{
    return extract_number<AIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(uint16_t &pInt)
{
    return extract_number<AUIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(int32_t &pInt)
{
    return extract_number<AIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(uint32_t &pInt)
{
    return extract_number<AUIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(int64_t &pInt)
{
    return extract_number<AIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(uint64_t &pInt)
{
    return extract_number<AUIntExtractor>(*this,pInt);
}

CTextReader &CTextReader::operator>>(float &pReal)
{
    return extract_number<ARealExtractor>(*this,pReal);
}

CTextReader &CTextReader::operator>>(double &pReal)
{
    return extract_number<ARealExtractor>(*this,pReal);
}

CTextReader &CTextReader::operator>>(long double &pReal)
{
    return extract_number<ARealExtractor>(*this,pReal);
}

/*namespace io*/ } /*namespace buola*/ }
